package com.hex.ds.hdtp.core.app.script.transfer.handler.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.hex.ds.hdtp.core.app.common.enums.BasicListStatus;
import com.hex.ds.hdtp.core.app.common.enums.ScriptTableConsanguinityFrom;
import com.hex.ds.hdtp.core.app.common.util.FileEncodeUtil;
import com.hex.ds.hdtp.core.app.common.util.JaveShellUtil;
import com.hex.ds.hdtp.core.app.metadata.service.ISourceConfInfoService;
import com.hex.ds.hdtp.core.app.script.common.dto.response.ScriptTableConsanguinityResponseDto;
import com.hex.ds.hdtp.core.app.script.common.service.IScriptTableConsanguinityService;
import com.hex.ds.hdtp.core.app.script.transfer.converter.IScriptVerifyLogConverter;
import com.hex.ds.hdtp.core.app.script.transfer.dto.request.ScriptConvertTargetListRequestDto;
import com.hex.ds.hdtp.core.app.script.transfer.dto.response.ScriptConvertTargetListResponseDto;
import com.hex.ds.hdtp.core.app.script.transfer.dto.response.ScriptConvertTaskResponseDto;
import com.hex.ds.hdtp.core.app.script.transfer.handler.IScriptVerifyHandlerService;
import com.hex.ds.hdtp.core.app.script.transfer.service.IScriptConvertTargetListService;
import com.hex.ds.hdtp.core.app.script.transfer.service.IScriptConvertTaskService;
import com.hex.ds.hdtp.core.app.script.transfer.service.IScriptVerifyLogService;
import com.hex.ds.hdtp.core.inf.common.exception.SourceJDBCException;
import com.hex.ds.hdtp.core.inf.common.exception.SourceJDBCExceptionDetail;
import com.hex.ds.hdtp.core.inf.common.utils.JdbcConnectUtil;
import com.hex.ds.hdtp.core.inf.data.contrast.service.impl.DataInfoSelectHandlerContext;
import com.hex.ds.hdtp.core.inf.metadata.po.SourceConfInfoPo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.hex.hdtp.common.utils.FileUtil.getFileContent;

/**
 * @Package com.hex.ds.hdtp.core.app.script.transfer.handler.impl
 * @ClassName ScriptVerifyHandlerService
 * @Description 脚本验证Handle服务
 * @Author gj.xu
 * @Date 2024/3/4 18:48
 * @Version v2.0
 **/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy, @Autowired})
public class ScriptVerifyHandlerService implements IScriptVerifyHandlerService {

    @Value("${python.exe}")
    private String pythonExe;

    private static final String DEFAULTBIZDATE = "19700101";

    private final IScriptTableConsanguinityService scriptTableConsanguinityService;

    private final IScriptConvertTargetListService scriptConvertTargetListService;

    private final IScriptVerifyLogService scriptEmptyTestLogService;

    private final IScriptVerifyLogConverter scriptEmptyTestLogConverter;

    private final DataInfoSelectHandlerContext dataInfoServiceataInfoService;

    private final IScriptConvertTaskService scriptConvertTaskService;

    private final ISourceConfInfoService sourceConfInfoService;

    private final JaveShellUtil javeShellUtil;

    private final  String LINEFEED = System.lineSeparator();

    @Value("${servers.isUse}")
    private boolean isUse;

    @Override
    @Async("scriptVerifyAsyncTaskExecutor")
    public void scriptVerifyHandler(ScriptConvertTargetListResponseDto scriptConvertTargetList) {
        Map<String, String> resultMap = new HashMap();

        // impala-maxcompute 相关脚本转换后无需验证
        if (scriptConvertTargetList.getSourceConnCode().toUpperCase().equals("IMPALA")
                && (scriptConvertTargetList.getTargetConnCode().toUpperCase().equals("ODPS") || scriptConvertTargetList.getTargetConnCode().toUpperCase().equals("PYODPS"))) {
            updateScriptVerifyConf(scriptConvertTargetList.getPkId(), BasicListStatus.PROCESSING.name(), null);
            updateScriptVerifyConf(scriptConvertTargetList.getPkId(), BasicListStatus.SUCCESS.name(), null);
            return;
        }

        try {
            // ① 更新脚本验证状态
            updateScriptVerifyConf(scriptConvertTargetList.getPkId(), BasicListStatus.PROCESSING.name(), null);
            // ② 验证脚本中涉及到的表是否存在
            checkTableExist(scriptConvertTargetList.getPkId(), resultMap);
            // ③ 执行脚本
            // resultMap = toScriptTest(scriptConvertTargetList);
            // TODO 这里判断文件为sql脚本则用JDBC方式执行SQL文件，后期考虑使用策略模式实现
            if (StrUtil.endWithIgnoreCase(scriptConvertTargetList.getConvertFileName(), "sql")) {
                String sqlContent = getFileContent(scriptConvertTargetList.getConvertFileAbsolutePath());
                List<SourceConfInfoPo> sourceConfInfoPoList = sourceConfInfoService.queryListBySourceCode(scriptConvertTargetList.getTargetConnCode());
                SourceJDBCException jdbcException = JdbcConnectUtil.runSqls(sourceConfInfoPoList, sqlContent, true);
                resultMap.putIfAbsent("SHELL_RESULT", jdbcException.isExecutionSuccess() ? "0" : "1");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.append(StrUtil.format("执行SQL脚本完成，总共 {} 段SQL，成功 {} 段，失败 {} 段。{}", jdbcException.getTotalSqlCount(), jdbcException.getSuccessCount(), jdbcException.getFailureCount(), LINEFEED + LINEFEED));
                for (int i = 0; i < jdbcException.getJdbcDetail().size(); i++) {
                    SourceJDBCExceptionDetail jdbcDetail = jdbcException.getJdbcDetail().get(i);
                    messageBuilder.append(StrUtil.format("第 {} 段SQL执行{}: {}", jdbcDetail.getIndex(), jdbcDetail.isSuccess() ? "成功" : "失败", LINEFEED));
                    messageBuilder.append(StrUtil.format("  {}{}", jdbcDetail.getSql(), LINEFEED + LINEFEED));
                    if (!jdbcDetail.isSuccess()) {
                        messageBuilder.append(StrUtil.format("第 {} 段SQL失败原因: {}{}{}", jdbcDetail.getIndex(), LINEFEED, jdbcDetail.getErrorMessage(), LINEFEED + LINEFEED));
                    }
                }
                resultMap.putIfAbsent("SHELL_ERR_RESULT", messageBuilder.toString());
            } else {
                resultMap = toScriptTest(scriptConvertTargetList);
            }
        } catch (Exception e) {
            log.error("脚本【{}】验证失败: {}", scriptConvertTargetList.getConvertFileName(), e.getMessage(), e);
            resultMap.putIfAbsent("SHELL_RESULT", "1");
            resultMap.putIfAbsent("SHELL_ERR_RESULT", e.getMessage());
        } finally {
            afterToScriptVerify(scriptConvertTargetList.getPkId(), resultMap);
        }
    }

    /**
     * @Method updateScriptVerifyConf
     * @Param scriptConvertTargetPkId
     * @Param verifyStatus
     * @Param errorMessage
     * @Return void
     * @Description 更新脚本验证状态
     * @Author Yin.Yang
     * @Date 2024/3/14 11:29
     * @Version V1.0
     */
    private void updateScriptVerifyConf(String scriptConvertTargetPkId, String verifyStatus, String errorMessage) {
        ScriptConvertTargetListRequestDto requestDto = new ScriptConvertTargetListRequestDto();
        requestDto.setPkId(scriptConvertTargetPkId).setEmptyTestStatus(verifyStatus).setErrorMessage(errorMessage);
        if (BasicListStatus.PROCESSING.name().equals(verifyStatus)) {
            requestDto.setStartTime(DateUtil.now());
        } else {
            requestDto.setEndTime(DateUtil.now());
        }
        scriptConvertTargetListService.modifyById(requestDto);
    }

    /**
     * @Method checkTableExist
     * @Param scriptConvertTargetPkId
     * @Param resultMap
     * @Return void
     * @Description 验证脚本中涉及到的表是否存在
     * @Author Yin.Yang
     * @Date 2024/3/14 14:55
     * @Version V1.0
     */
    private void checkTableExist(String scriptConvertTargetPkId, Map<String, String> resultMap) {
        List<ScriptTableConsanguinityResponseDto> scriptTableConsanguinitys =
                scriptTableConsanguinityService.queryList(scriptConvertTargetPkId, ScriptTableConsanguinityFrom.SCRIPT_CONVERT_TARGET.getName());
        List<String> nonExistentTables = new ArrayList<>();
        scriptTableConsanguinitys.forEach(scriptTableConsanguinity -> {
            try {
                ScriptConvertTaskResponseDto scriptConvertInfo = scriptConvertTaskService.queryById(scriptTableConsanguinity.getTaskPkId());
                String targetDbType = StrUtil.split(scriptConvertInfo.getConvertType(), '-').get(1);
                String schema = StrUtil.contains(scriptTableConsanguinity.getTableName(), '.') ?
                        StrUtil.subBefore(scriptTableConsanguinity.getTableName(), '.', false) : "";
                String table = StrUtil.contains(scriptTableConsanguinity.getTableName(), '.') ?
                        StrUtil.subAfter(scriptTableConsanguinity.getTableName(), '.', false) : scriptTableConsanguinity.getTableName();
                Boolean tableExist = dataInfoServiceataInfoService.getDataInfoService(targetDbType).selectTableExist(
                        scriptConvertInfo.getTargetConnCode(), schema, table);
                if (!tableExist) {
                    log.error("表【{}】不存在", scriptTableConsanguinity.getTableName());
                    nonExistentTables.add(scriptTableConsanguinity.getTableName());
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });
        if (!nonExistentTables.isEmpty()) {
            resultMap.putIfAbsent("SHELL_RESULT", "1");
            resultMap.putIfAbsent("SHELL_ERR_RESULT", "以下表不存在：\n" + String.join("\n", nonExistentTables));
            throw new RuntimeException("脚本验证失败");
        }
    }




    /*
     * @Method: toScriptTest <br>
     * @Param: [scriptDataTestListResponseDto] <br>
     * @Return: void <br>
     * @Description：执行脚本验证<br>
     * @Author： wz.li<br>
     * @Date： 2023/8/4 17:47 <br>
     * @Version： V2.0.2<br>
     */
    private Map toScriptTest(ScriptConvertTargetListResponseDto scriptConvertTargetList) {
        String shell = StrUtil.format("{} {} {}", pythonExe, scriptConvertTargetList.getConvertFileAbsolutePath(), DEFAULTBIZDATE);
        if (isUse) return javeShellUtil.executeScriptShell(shell);
        return JaveShellUtil.ExecCommand(shell);
    }

    /*
     * @Method: afterToScriptVerify <br>
     * @Param: [scriptConvertTargetList, resultMap] <br>
     * @Return: void <br>
     * @Description：脚本验证完成后数据更新<br>
     * @Author： wz.li<br>
     * @Date： 2023/8/14 20:47 <br>
     * @Version： V2.0.2<br>
     */
    private void afterToScriptVerify(String convertTargetListPkId, Map resultMap) {
        String status = String.valueOf(resultMap.get(JaveShellUtil.SHELL_RESULT)).equals("0") ? BasicListStatus.SUCCESS.name() : BasicListStatus.FAIL.name();
        updateScriptVerifyConf(convertTargetListPkId, status, String.valueOf(resultMap.get(JaveShellUtil.SHELL_ERR_RESULT)));

        ScriptConvertTargetListResponseDto scriptConvertTargetList = scriptConvertTargetListService.queryById(convertTargetListPkId);
        scriptEmptyTestLogService.add(scriptEmptyTestLogConverter.toScriptEmptyTestLogRequestDto(scriptConvertTargetList));
    }

    /**
     * @Method getFileContent
     * @Param filePath
     * @Return java.lang.String
     * @Description 读取指定文件内容
     * @Author Yin.Yang
     * @Date 2024/3/13 17:52
     * @Version V1.0
     */
    private String getFileContent(String filePath) {
        String convertFileAbsolutePath = FilenameUtils.separatorsToUnix(filePath);
        if (FileUtil.exist(convertFileAbsolutePath)) {
            return FileUtil.readString(convertFileAbsolutePath, FileEncodeUtil.getFileEncode(convertFileAbsolutePath));
        }
        return "";
    }

}
